#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
THN DATA ABSTRACTION LAYER version 1

Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

This library is dual-licensed.
  + For commercial software, please obtain a commercial license from THN Solutions LLC.

  + For free software, this library is licensed under GPL version 3. A summary of GPLv3 is
    listed below. You should also find a copy of GPLv3 as file License_GPLv3.txt included
	with the source-code files.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as published by
    the Free Software Foundation  of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

#region Using directives
using System;
using System.Collections;
using System.Reflection;
using Thn.Application;
using Thn.Reflection;
#endregion

namespace Thn.Data.Mapping
{
    /// <summary>
    /// A data descriptor using reflection technique
    /// </summary>
    public class ReflectionDataMapper : AbstractDataMapper
    {
        #region Get Entity
        /// <summary>
        /// Gets mapping for an object type
        /// </summary>
        protected override ObjectMap OnGetEntity(Type objectEntity)
        {
            ObjectMap result = null;

            //get Table attribute
            object[] table = objectEntity.GetCustomAttributes(typeof(TableAttribute), true);
                       
            if ((table != null) && (table.Length > 0))
            {
                //attribute is set, use table specified in attribute
                TableAttribute ta = (TableAttribute)table[0];
                result = new ObjectMap(objectEntity, ta.Name);
            }
            else
            {
                //not set, use the object name
                result = new ObjectMap(objectEntity, objectEntity.Name);
            }

            return result;
        }
        #endregion

        #region Create Attribute Map Instance
        /// <summary>
        /// Construct a new instance of AttributeMap
        /// </summary>
        protected override AttributeMap CreateAttributeMapInstance()
        {
            return new ReflectionAttributeMap();
        }
        #endregion

        #region Get Attributes
        /// <summary>
        /// Gets savable attribute(fields, properties) for a given object type
        /// </summary>
        protected override AttributeMap[] OnGetAttributes(Type objectEntity)
        {
            ReflectionAttributeMap[] result = null;  
            Type attributeType = null;

            //reflect for Savable attributes
            ObjectReflector reflector = new ObjectReflector();
            MemberInfoCollection infos = reflector.GetMembers(objectEntity, true, true, typeof(SavableAttribute), false, true);

            result = new ReflectionAttributeMap[infos.Count];

            //build map
            if (infos.Count > 0)
            {
                int idx = 0;

                for (int i = 0; i <infos.Count ; i++)
                {
                    ReflectionAttributeMap map = CreateAttributeMapInstance() as ReflectionAttributeMap;
                    map.MemberInfo = infos[i];

                    //check if attribute is marked with Column attribute
                    object[] columns = infos[i].GetCustomAttributes(typeof(ColumnAttribute), true);
                    if ((columns != null) && (columns.Length > 0))
                    {//is decorated with a Column attribute
                        ColumnAttribute col = columns[0] as ColumnAttribute;

                        if (col.Name != "") map.DataAttribute = col.Name;
                        else map.DataAttribute = infos[i].Name;

                        map.ObjectAttribute = infos[i].Name;
                        map.IsRequired = col.Required;
                        map.Size = col.Size;
                        if (col.DbTypeSpecified) map.DataAttributeType = col.Type;
                    }                    
                    else
                    {//not decorated with a Column attribute
                        map.DataAttribute = infos[i].Name;
                        map.ObjectAttribute = infos[i].Name;                    
                    }

                    //attribute type                    
                    if (infos[i] is FieldInfo) attributeType = ((FieldInfo)infos[i]).FieldType;
                    else if (infos[i] is PropertyInfo) attributeType = ((PropertyInfo)infos[i]).PropertyType;
                    map.ObjectAttributeType = attributeType;

                    //support nullable type
                    if (TypeExtensions.IsNullableType(attributeType))
                    {
                        map.IsRequired = false;

                        //get the underlying type. eg if "Nullable<int>" then this will return just "int"
                        map.ObjectAttributeType = map.ObjectAttributeType.GetGenericArguments()[0];
                    }

                    //is special type
                    if (attributeType.IsEnum) map.IsEnum = true;
                    else if (attributeType.IsArray) map.IsArray = true;
                    else if (attributeType.GetInterface(typeof(System.Collections.IList).Name) != null) map.IsList = true;

                    //is identifier 
                    object[] identifiers = infos[i].GetCustomAttributes(typeof(IdentifierAttribute), true);
                    if ((identifiers != null) && (identifiers.Length > 0))
                    {
                        IdentifierAttribute ia = (IdentifierAttribute)identifiers[0];
                        map.IsIdentifier = true;
                        if (ia.Auto) map.IsAutoGenerated = true;
                    }

                    result[idx] = map;
                    idx++;
                }//for
            }//count >0

            return result;
        }
        #endregion

        #region Set Object Attribute value
        /// <summary>
        /// Assign value to object's attribute
        /// </summary>
        /// <param name="target">object to set</param>
        /// <param name="attribute">name of object's attribute</param>
        /// <param name="value">value to set</param>
        public override void SetObjectAttributeValue(object target, string attribute, object value)
        {
            AttributeMap attributeMap = GetObjectAttributeMap(target.GetType(), attribute);
            ((ReflectionAttributeMap)attributeMap).SetValue(target, value);
        }

        /// <summary>
        /// Assign value to object's attribute
        /// </summary>
        /// <param name="target">object to set</param>
        /// <param name="attribute">attribute map to assign value</param>
        /// <param name="value">value to set</param>
        public override void SetObjectAttributeValue(object target, AttributeMap attribute, object value)
        {
            ((ReflectionAttributeMap)attribute).SetValue(target, value);
        }
        #endregion

        #region Get Object Attribute Value
        /// <summary>
        /// Get value from object's attribute and return (in data attribute type) value
        /// </summary>
        /// <param name="target">object to obtain value from</param>
        /// <param name="attribute">name of object's attribute</param>
        /// <returns>value in data attribute's type</returns>
        public override object GetObjectAttributeValue(object target, string attribute)
        {
            object result = null;

            AttributeMap attributeMap = GetObjectAttributeMap(target.GetType(), attribute);
            ReflectionAttributeMap map = (ReflectionAttributeMap)attributeMap;
            result = map.GetValue(target);

            //normalize value so that value should not be null
            //if (result == null)
            //{
            //    result = DefaultValues.Get(map.ObjectAttributeType);
            //}

            return result;
        }

        /// <summary>
        /// Get value from object's attribute
        /// </summary>
        /// <param name="target">object to obtain value from</param>
        /// <param name="attribute">attribute map to obtain value</param>
        public override object GetObjectAttributeValue(object target, AttributeMap attribute)
        {
            object result = null;

            ReflectionAttributeMap map = (ReflectionAttributeMap)attribute;
            result = map.GetValue(target);

            //normalize value so that value should not be null
            if (result == null) result = DefaultValues.Get(map.ObjectAttributeType);

            return result;
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        public ReflectionDataMapper()
        {
        }
        #endregion
    }
}
